<?php

/*
current problems
-problem with some encoding stuff?

examples that work
http://www.mcfedries.com/CreatingAWebPage/defnlist.htm
http://alistapart.com/d/cssmaps/mapfinal/
http://www.cssplay.co.uk/menus/drop_definition4.html

doesn't work 'cause the parser is bad
http://web-graphics.com/mtarchive/ItalianMenu.html
from http://www.w3.org/2003/glossary/ (or is this cause it's invalid?)

don't work 'cause invalid html (but valid <dl>s)
http://obscuretags.com/showcode.php?id=1
*/

/**
 * HTML/XML Parser Class from http://www.phpit.net/code/html-parser-class/
 *
 * This is a helper class that is used to parse HTML and XML. A unique feature of this parsing class
 * is the fact that it includes support for innerHTML (which isn't easy to do).
 *
 * @author Dennis Pallett
 * @copyright Dennis Pallett 2006
 * @package HTML_Parser
 * @version 1.0
 */
// Helper Class
// To parse HTML/XML
Class HTML_Parser {
    // Private properties
    var $_parser;
    var $_tags = array();
    var $_html;
    var $output = array();
    var $strXmlData;
    var $_level = 0;
    var $_outline;
    var $_tagcount = array();
    var $xml_error = false;
    var $xml_error_code;
    var $xml_error_string;
    var $xml_error_line_number;

    function get_html () {
        return $this->_html;
    }

    function parse($strInputXML) {
        $this->output = array();

        // Translate entities
        $strInputXML = $this->translate_entities($strInputXML);

        $this->_parser = xml_parser_create ();
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, true);
        xml_set_object($this->_parser,$this);
        xml_set_element_handler($this->_parser, "tagOpen", "tagClosed");

        xml_set_character_data_handler($this->_parser, "tagData");

        $this->strXmlData = xml_parse($this->_parser,$strInputXML );

        if (!$this->strXmlData) {
            $this->xml_error = true;
            $this->xml_error_code = xml_get_error_code($this->_parser);
            $this->xml_error_string = xml_error_string(xml_get_error_code($this->_parser));
            $this->xml_error_line_number =  xml_get_current_line_number($this->_parser);
            return false;
        }

        return $this->output;
    }


    function tagOpen($parser, $name, $attr) {
        $this->_level++; // Increase level
        $newtag = $this->create_tag($name, $attr); // Create tag:
        $tag = array("name"=>$name,"attr"=>$attr, "level"=>$this->_level); // Build tag
        array_push ($this->output, $tag); // Add tag
        $this->_tags[$this->_level] = $tag; // Add tag to this level
        $this->_html .= $newtag; // Add to HTML
        $this->_outline .= $this->_level . $newtag;// Add to outline
    }

    function create_tag($name, $attr) {
        // Create tag:
        # Begin with name
        $tag = '<' . strtolower($name) . ' ';
        # Create attribute list
        foreach ($attr as $key=>$val) {
            $tag .= strtolower($key) . '="' . htmlentities($val) . '" ';
        }
        # Finish tag
        $tag = trim($tag);
        switch(strtolower($name)) {
            case 'br':
            case 'input':
                $tag .= ' /';
            break;
        }
        $tag .= '>';
        return $tag;
    }

    function tagData($parser, $tagData) {
        if(trim($tagData)) {
            if(isset($this->output[count($this->output)-1]['tagData'])) {
                $this->output[count($this->output)-1]['tagData'] .= $tagData;
            } else {
                $this->output[count($this->output)-1]['tagData'] = $tagData;
            }
        }
        $this->_html .= htmlentities($tagData);
        $this->_outline .= htmlentities($tagData);
    }

    function tagClosed($parser, $name) {
        // Add to HTML and outline
        switch (strtolower($name)) {
            case 'br':
            case 'input':
                break;
            default:
            $this->_outline .= $this->_level . '</' . strtolower($name) . '>';
            $this->_html .= '</' . strtolower($name) . '>';
        }

        // Get tag that belongs to this end
        $tag = $this->_tags[$this->_level];
        $tag = $this->create_tag($tag['name'], $tag['attr']);

        // Try to get innerHTML
        $regex = '%' . preg_quote($this->_level . $tag, '%') . '(.*?)' . preg_quote($this->_level . '</' . strtolower($name) . '>', '%') . '%is';
        preg_match ($regex, $this->_outline, $matches);

        // Get innerHTML
        if (isset($matches['1'])) {
            $innerhtml = $matches['1'];
        }

        // Remove level identifiers
        $this->_outline = str_replace($this->_level . $tag, $tag, $this->_outline);
        $this->_outline = str_replace($this->_level . '</' . strtolower($name) . '>', '</' . strtolower($name) . '>', $this->_outline);

        // Add innerHTML
        if (isset($innerhtml)) {
            $this->output[count($this->output)-1]['innerhtml'] = $innerhtml;
        }

        // Fix tree
        $this->output[count($this->output)-2]['children'][] = $this->output[count($this->output)-1];
        array_pop($this->output);

        // Decrease level
        $this->_level--;
    }

    function translate_entities($xmlSource, $reverse =FALSE) {
        static $literal2NumericEntity;
        if (empty($literal2NumericEntity)) {
            $transTbl = get_html_translation_table(HTML_ENTITIES);
            foreach ($transTbl as $char => $entity) {
                if (strpos('&#038;"<>', $char) !== FALSE) continue;
                    $literal2NumericEntity[$entity] = '&#'.ord($char).';';
                }
            }
            if ($reverse) {
                return strtr($xmlSource, array_flip($literal2NumericEntity));
            } else {
                return strtr($xmlSource, $literal2NumericEntity);
            }
      }
}







function get_file($u, $params=false, $method=false) {
	$session = curl_init($u);
	if (strtolower($method) == 'post') {
		curl_setopt($session, CURLOPT_POST, true);
		if ($params) { curl_setopt($session, CURLOPT_POSTFIELDS, $params); }
		}
	curl_setopt($session, CURLOPT_HEADER, false); // don't return headers
	curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // but do return the response
	$response = curl_exec($session);
	curl_close($session);
	return $response;
	}

function definitions_from_string($string) {
	$string = html_entity_decode($string);
	foreach (explode("\n", $string) as $d) {
		$d = explode('|x|', $d);
		if (count($d) != 2) { $d = explode('FOOO', ereg_replace('^([^:]+):(.+)$', "\\1FOOO\\2", implode('', $d))); }
		if (count($d)==2) {
			$def = trim($d[1]);
			if (strpos($def, 'javascript') !== false) { $def = strip_tags($def); }
			$defs[] = array ( 'term' => strip_tags(trim($d[0])), 'def' => $def );
			}
		}
	return $defs;
	}

function parse_dl($nodes) {
	global $parsed_defs;
	$nodes = $nodes['children'];
	if (!$nodes) { return false; }
	foreach ($nodes as $node) {
		if ($node['name'] == 'DT') {
			if (!$last_was_term) { //start of new definition, end the previous one
				if ($terms && $defs) { $parsed_defs[] = array ('terms' => $terms, 'defs' => $defs); }
				$terms = false;
				$defs = false;
				}
			$terms[] = trim(strip_tags($node['innerhtml']));
			$last_was_term = true;
			}
		elseif ($node['name'] == 'DD') {
			$defs[] = trim(ereg_replace("\n", ' ', $node['innerhtml']));
			$last_was_term = false;
			}
		}
	if ($terms && $defs) { $parsed_defs[] = array ('terms' => $terms, 'defs' => $defs); } //handles the last def in a <dl>
	}

function find_dls($nodes) {
	global $dls;
	foreach ($nodes as $node) {
		if ($node['name'] == 'DL') { $dls[] = $node; }
		elseif ($node['children']) { find_dls($node['children']); }
		}
	}

function get_definitions() {
	$f = gi('definitionfile');
	if ($f) {
		$f = get_file($f);
		$parser = new HTML_Parser;
		$parsed = $parser->parse($f);
if ($parsed) { echo "<!-- parsed as html -->"; } //debug foo
else { echo "<!-- NOT parsed as html -->"; } //debug foo
		if ($parsed) {
			global $dls, $parsed_defs;
			find_dls($parsed);
			if ($dls) {
				foreach ($dls as $dl) { parse_dl($dl); }
				if ($parsed_defs) {
					foreach ($parsed_defs as $item) {
						foreach ($item['terms'] as $term) {
							foreach ($item['defs'] as $def) { $defs[] = array ('term' => $term, 'def' => $def); }
							}
						}
					if ($defs) {
						foreach ($defs as $def) { $tmp[] = implode('|x|', $def); }
						$_POST['definitions'] = implode("\n", $tmp);
						return $defs;
						}
					}
				}
			}
		elseif ($f == strip_tags($f)) { //not parsed as html but could be invalid html
			$defs = definitions_from_string($f);
			if ($defs) {
				$_POST['definitions'] = $f;
				return $defs;
				}
			}
		}
	$dstring = gi('definitions');
	if ($dstring) { $defs = definitions_from_string($dstring); }
	if ($defs) { return $defs; }
	return array (
		array ('term' => 'green', 'def' => 'a greenish colour'),
		array ('term' => 'blue', 'def' => 'a blueish colour'),
		array ('term' => 'orange', 'def' => 'an orangeish colour'),
		array ('term' => 'brown', 'def' => 'a brownish colour that is oh so very brown and such, especially when it is brown, which it tends to be rather, most of the time'),
		array ('term' => 'red', 'def' => 'a redish colour')
		);
	}

?>